package com.bestvike.stone.core.lang;

/**
 * SnowFlake 雪花算法
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
 * <p>
 * 第 1 位为符号位未使用
 * 接下来的 41 位为毫秒级时间 (41 位的长度可以使用 69 年)
 * 然后是 5 位 datacenterId 和 5 位 workerId (10 位的长度最多支持部署 1024 个节点）
 * 最后 12 位是毫秒内的计数（12 位的计数顺序号支持每个节点每毫秒产生 4096 个 Id 序号）
 * 一共加起来刚好 64 位，为 Long 类型 (转换成字符串后长度最多 19 位，最大值参考 Long.MAX_VALUE)
 * <p>
 * SnowFlake 生成的 Id 整体上按照时间自增排序，并且整个分布式系统内不会产生碰撞（由 datacenterId 和 workerId 作区分），并且效率较高。经测试 SnowFlake 每秒能够产生 26 万个 Id。
 */
public final class SnowFlake {
    /**
     * 起始时刻 2019-01-01
     */
    private static final long EPOCH = 1546272000000L;

    /**
     * 每一部分占用的位数
     */
    private static final long DATACENTER_ID_BITS = 5L;  //集群标识位数
    private static final long WORKER_ID_BITS = 5L;      //机器标识位数
    private static final long SEQUENCE_BITS = 12L;      //序列号位数

    /**
     * 每一部分的最大值
     */
    private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);

    /**
     * 每一部分向左的位移
     */
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private static final long DATACENTER_ID_SHIFT = WORKER_ID_SHIFT + WORKER_ID_BITS;
    private static final long TIMESTAMP_SHIFT = DATACENTER_ID_SHIFT + DATACENTER_ID_BITS;

    private final long datacenterId;    //集群标识
    private final long workerId;        //机器标识
    private long sequence = 0L;         //序列号
    private long lastTimestamp = -1L;   //上一次时间戳

    /**
     * 构造函数
     *
     * @param datacenterId 集群标识
     * @param workerId     机器标识
     */
    public SnowFlake(long datacenterId, long workerId) {
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0)
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_ID));
        if (workerId > MAX_WORKER_ID || workerId < 0)
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID));

        this.datacenterId = datacenterId;
        this.workerId = workerId;
    }

    /**
     * 生成一个 Id
     */
    public synchronized long nextId() {
        long timestamp = this.timeGen();
        if (timestamp < this.lastTimestamp)
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", this.lastTimestamp - timestamp));

        if (timestamp == this.lastTimestamp) {
            //相同毫秒内，序列号自增
            this.sequence = (this.sequence + 1L) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (this.sequence == 0L) {
                timestamp = this.tilNextMillis();
            }
        } else {
            //不同毫秒内，序列号置为 0
            this.sequence = 0L;
        }
        this.lastTimestamp = timestamp;
        return (timestamp - EPOCH) << TIMESTAMP_SHIFT       //时间戳
                | this.datacenterId << DATACENTER_ID_SHIFT  //集群标识
                | this.workerId << WORKER_ID_SHIFT          //机器标识
                | this.sequence;                            //序列号
    }

    private long tilNextMillis() {
        long timestamp = this.timeGen();
        while (timestamp <= this.lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}
